#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/uaccess.h>


#include "ili9488_cmds.h"
#include "ili9488_regs.h"

#include "logo.h"

/*****************************************************
ili9488 spi clock cycle write 66ns, bit rate = 15.15M, max fps: 7
*****************************************************/
/* color sequence: bgr
#define  RED  (BIT(15) | BIT(14) | BIT(13) | BIT(12) | BIT(11))
#define  GREEN  (BIT(10) | BIT(9) | BIT(8) | BIT(7) | BIT(6) | BIT(5))
#define  BLUE  (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4))
*/

static int major;
static struct class *ili9488_class;

static struct ili9488_par *par;

//static struct gpio_desc *led;
static u8 *buffer;

inline void Lcd_Write_Cmd(u8 cmd);
inline void Lcd_Write_Data(u8 *data, size_t len);

void ili9488_write_reg8_bus8(int len, ...) {
	int i;
	int para;
	va_list args;
	va_start(args, len);
	para = va_arg(args, int);

	Lcd_Write_Cmd(para);

	for (i = 0; i < len - 1; i++) {
		para = va_arg(args, int);
		*(buffer + i) = para;
	}
	va_end(args);

	if (len - 1 > 0) {
		Lcd_Write_Data(buffer, len - 1);
	}
}

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))

#define write_reg(...)  \
do {                     \
	ili9488_write_reg8_bus8(NUMARGS(__VA_ARGS__), __VA_ARGS__);   \
} while (0)


struct ili9488_par {
	struct spi_device *spi;
	struct platform_device *pdev;

	spinlock_t lock;
	
	struct {
		void *buf;
		size_t len;
	} txbuf;

	struct {
		struct gpio_desc *reset;
		struct gpio_desc *dc;
	} gpio;
};

int ili9488_write_spi(struct ili9488_par *par, void *buf, size_t len) {
	int ret;
	struct spi_transfer t = { .tx_buf = buf, .len = len, };
	struct spi_message m;

	if (!par->spi) {
		pr_err("spi is NULL\n");
		return -1;
	}
	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	ret = spi_sync(par->spi, &m);
	if (ret < 0){
		pr_err("ili9488_write_spi(len=%ld),fail \n", len);
	}
	return ret;
}

/* 16 bit pixel over 8-bit databus */
int ili9488_write_vmem16_bus8(struct ili9488_par *par, u16 *data, size_t offset, size_t len) {
	u16 pixel, r, g, b, r8, g8, b8;
	u8 *txbuf = par->txbuf.buf;
	size_t remain, to_copy, tx_array_size;
	int i, nstripe = 0;
	int ret = 0;

	/* remaining number of pixels to send */
	remain = len;

	gpiod_set_raw_value(par->gpio.dc, 1);

	/* number of pixels that fits in the transmit buffer */
	tx_array_size = par->txbuf.len / 3;

	while (remain) {
		/* number of pixels to copy in one iteration of the loop */
		to_copy = min(tx_array_size, remain);

		//dev_info(&par->spi->dev, "to_copy=%zu, remain=%zu\n", to_copy, remain - to_copy);

		for (i = 0; i < to_copy; i++) {
			pixel = data[nstripe * to_copy + i];
			b = pixel & 0x1f;
			g = (pixel & (0x3f << 5)) >> 5;
			r = (pixel & (0x1f << 11)) >> 11;

			r8 = (r & 0x1F) << 3;
			g8 = (g & 0x3F) << 2;
			b8 = (b & 0x1F) << 3;
			
			txbuf[i * 3 + 0] = r8;
			txbuf[i * 3 + 1] = g8;
			txbuf[i * 3 + 2] = b8;
		}
		
		//dev_info(&par->spi->dev, "to_copy %ld  \n", to_copy * 3);
		
		ret = ili9488_write_spi(par, par->txbuf.buf, to_copy * 3);
		if (ret < 0)
			return ret;
		remain -= to_copy;
		nstripe++;
	}
	
	return 0;
}


inline void lcd_write(u8 *data, size_t len) {
	ili9488_write_spi(par, data, len);
}

inline void Lcd_Write_Cmd(u8 cmd) {
	gpiod_set_raw_value(par->gpio.dc, 0);
	lcd_write(&cmd, 1);
}

inline void Lcd_Write_Data(u8 *data, size_t len) {
	gpiod_set_raw_value(par->gpio.dc, 1);
	lcd_write(data, len);
}

void lcd_set_addr_win(int x_start, int y_start,
		int x_end, int y_end) {
	/* Column address set */
	write_reg(ILI9488_CMD_COLUMN_ADDRESS_SET, (x_start >> 8) & 0xFF,
			x_start & 0xFF, (x_end >> 8) & 0xFF, x_end & 0xFF);

	/* Row adress set */
	write_reg(ILI9488_CMD_PAGE_ADDRESS_SET, (y_start >> 8) & 0xFF,
			y_start & 0xFF, (y_end >> 8) & 0xFF, y_end & 0xFF);

	/* Memory write */
	write_reg(ILI9488_CMD_MEMORY_WRITE);
}


int lcd_clear(u16 color) {
	size_t offset, len;
	int ret = 0;
	u16 *clrbuf;
	
	offset = 0;
	len = ILI9488_TFTWIDTH * ILI9488_TFTHEIGHT;

	clrbuf = (u16 *)kmalloc(len, GFP_KERNEL);
	memset(clrbuf, color, len);
	
	lcd_set_addr_win(0, 0, ILI9488_TFTWIDTH - 1, ILI9488_TFTHEIGHT - 1);
	ret = ili9488_write_vmem16_bus8(par, clrbuf, offset, len);

	kfree(clrbuf);
	
	return ret;
}

int lcd_rect(u16 *data, int start_x, int start_y,
		int width, int height) {
	size_t offset, len;
	int ret = 0;
	
	offset = 0;
	len = width * height;
	
	lcd_set_addr_win(start_x, start_y, start_x + width - 1,
			start_y + height - 1);
	ret = ili9488_write_vmem16_bus8(par, data, offset, len);
	
	return ret;
}

void lcd_reset(struct ili9488_par *par) {
	gpiod_set_raw_value(par->gpio.reset, 0);
	udelay(20);
	gpiod_set_raw_value(par->gpio.reset, 1);
	mdelay(120);
}

int lcd_init(void) {
	//reset
	lcd_reset(par);

	/* startup sequence for MI0283QT-9A */
	write_reg(ILI9488_SWRESET); /* software reset */
	mdelay(5);
	write_reg(ILI9488_DISPOFF); /* display off */

	write_reg(ILI9488_CMD_POSITIVE_GAMMA_CTRL, 0x00, 0x03, 0x09, 0x08,
			0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F);
	write_reg(ILI9488_CMD_NEGATIVE_GAMMA_CTRL, 0x00, 0x16, 0x19, 0x03,
			0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F);

	write_reg(ILI9488_CMD_POWER_CONTROL_1, 0x17, 0x15);
	write_reg(ILI9488_CMD_POWER_CONTROL_2, 0x41);

	write_reg(ILI9488_CMD_VCOM_CONTROL_1, 0x00, 0x12, 0x80);

	write_reg(ILI9488_CMD_MEMORY_ACCESS_CONTROL, 0x48);

	write_reg(ILI9488_CMD_INTERFACE_PIXEL_FORMAT, 0x66);
	write_reg(ILI9488_CMD_INTERFACE_MODE_CONTROL, 0x80);

	write_reg(ILI9488_CMD_FRAME_RATE_CONTROL_NORMAL, 0xA0);

	write_reg(ILI9488_CMD_DISPLAY_INVERSION_CONTROL, 0x02);
	write_reg(ILI9488_CMD_DISPLAY_FUNCTION_CONTROL, 0x02, 0x02);

	write_reg(ILI9488_CMD_SET_IMAGE_FUNCTION, 0x00);

	write_reg(ILI9488_CMD_ADJUST_CONTROL_3, 0xA9, 0x51, 0x2C, 0x82);

	write_reg(ILI9488_SLPOUT);
	mdelay(120);

	write_reg(ILI9488_DISPON);
	mdelay(20);

	//write_reg(0x36, 0x38);	//landscape screen
	write_reg(0x36, 0x0c);		//portrait screen
	
	return 0;
}


int ili9488_open (struct inode *node, struct file *file)
{
	return 0;
}

ssize_t ili9488_write (struct file *file, const char __user *user, size_t len, loff_t *loff)
{
	int ret, sx, sy, w, h;
	unsigned short *ker_buf;
	unsigned short *user_buf = (unsigned short*)user;

	ker_buf = (unsigned short*)kmalloc(len, GFP_KERNEL);
	ret = copy_from_user(ker_buf, user_buf, len*2);

	sx = ker_buf[0];
	sy = ker_buf[1];
	w = ker_buf[2];
	h = ker_buf[3];
	//dev_info(&par->spi->dev, "sx = %d, sy = %d, w = %d, h = %d", sx, sy, w, h);
	
	ret = lcd_rect(ker_buf+4, sx, sy, w, h);

	kfree(ker_buf);
	
	return ret; 
}

static const struct file_operations ili9488_fops = {
	.owner =	THIS_MODULE,
	.write =	ili9488_write,
	.open =		ili9488_open,
};

int ili9488_probe(struct spi_device *spi) {
	void __iomem * vaddr;

	int ret = 0, i;
	
	buffer = kmalloc(1024 * sizeof(u8), GFP_KERNEL);

	vaddr = ioremap(0x32b300e4, 8);		// A47:	 lcd d/c
	writel(0x246, vaddr);
	vaddr = ioremap(0x32b30058, 8);		// AJ53: lcd reset
	writel(0x245, vaddr);
	vaddr = ioremap(0x32b30140, 8);		// U49: lcd cs
	writel(0x42, vaddr);

	par = kmalloc(sizeof(struct ili9488_par), GFP_KERNEL);
	par->spi = spi;
	par->txbuf.buf = kmalloc(8192 * sizeof(u8), GFP_KERNEL);
	par->txbuf.len = 8192;
	dev_info(&spi->dev, "ili9488_probe start.\n");
	dev_info(&spi->dev, "master->bus_num = %d\n", spi->master->bus_num);
	dev_info(&spi->dev, "chip_select = %d\n", spi->chip_select);
	dev_info(&spi->dev, "bits_per_word = %d\n", spi->bits_per_word);

	/*
	led = devm_gpiod_get(&spi->dev, "led", GPIOD_OUT_HIGH);
	if (IS_ERR(led)) {
		pr_err("error \n");
		return -1;
	}
	*/
	par->gpio.reset = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH);
	if (IS_ERR(par->gpio.reset)) {
		pr_err("error \n");
		return -1;
	}

	par->gpio.dc = devm_gpiod_get(&spi->dev, "dc", GPIOD_OUT_HIGH);
	if (IS_ERR(par->gpio.dc)) {
		pr_err("error \n");
		return -1;
	}
	
	lcd_init();

	//lcd_clear(0x0);
	for (i = 0; i < ILI9488_TFTWIDTH*ILI9488_TFTHEIGHT; i++)
		logo[i] = (logo[i] << 8) | (logo[i] >> 8);
	lcd_rect(logo, 0, 0, ILI9488_TFTWIDTH, ILI9488_TFTHEIGHT);

	major = register_chrdev(0, "msp3520_module", &ili9488_fops);
	ili9488_class = class_create(THIS_MODULE, "spi_device");
	device_create(ili9488_class, &spi->dev, MKDEV(major, 0), NULL, "ili9488");
	
	return ret;
}

int ili9488_remove(struct spi_device *spi) {
	dev_info(&spi->dev, "msp3520 remove.");
	kfree(buffer);
	kfree(par->txbuf.buf);
	kfree(par);
		
	device_destroy(ili9488_class, MKDEV(major, 0));
	class_destroy(ili9488_class);
	dev_info(&spi->dev, "device name: %s", spi->dev.driver->name);
	unregister_chrdev(major, spi->dev.driver->name);
	
	return 0;
}

static const struct of_device_id tft9488_dt_ids[] = { 
	{.compatible =	"ilitek,ili9488" }, 
	{ }, 
};

MODULE_DEVICE_TABLE(of, tft9488_dt_ids);

static struct spi_driver ili9488_spi_driver = { 
	.driver = { 
		.name =	"ilitek,ili9488", 
		.of_match_table = of_match_ptr(tft9488_dt_ids), 
	},
	.probe = ili9488_probe, 
	.remove = ili9488_remove, 
};

module_spi_driver(ili9488_spi_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jasen");
MODULE_DESCRIPTION("ilitek ili9488 lcd driver");


